package loader

import (
	"context"
	"fmt"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/smart-snapshotter/pkg/types"
)

// SmartLoader implements intelligent on-demand loading with fanotify monitoring
type SmartLoader struct {
	config          types.LoaderConfig
	storage         types.StorageBackend
	cache           types.Cache
	monitor         *FanotifyMonitor

	// Loading management
	activeLoads     map[string]*LoadOperation
	loadMutex       sync.RWMutex

	// Prefetch management
	prefetchQueue   *PrefetchQueue

	// Event processing
	eventProcessor  *EventProcessor

	// Statistics
	stats           *LoaderStats
	statsMutex      sync.RWMutex

	// Control
	ctx             context.Context
	cancel          context.CancelFunc
	wg              sync.WaitGroup
}

// LoadOperation represents an active load operation
type LoadOperation struct {
	Key           string
	Status        string // "pending", "loading", "completed", "failed"
	BytesLoaded   int64
	BytesTotal    int64
	StartTime     time.Time
	EndTime       time.Time
	Error         error
	ProgressChan  chan types.LoadingProgress
	cancel        context.CancelFunc
}

// LoaderStats contains detailed loader statistics
type LoaderStats struct {
	TotalRequests      int64
	CacheHits          int64
	CacheMisses        int64
	PrefetchRequests   int64
	PrefetchHits       int64
	ActiveDownloads    int64
	TotalBytesLoaded   int64
	TotalBytesPrefetched int64
	AverageLoadTime    time.Duration
	AveragePrefetchTime time.Duration
	EventCount         int64
	TriggerCount       int64
	LastUpdated        time.Time
}

// NewSmartLoader creates a new smart loader
func NewSmartLoader(config types.LoaderConfig, storage types.StorageBackend, cache types.Cache) (*SmartLoader, error) {
	ctx, cancel := context.WithCancel(context.Background())

	loader := &SmartLoader{
		config:         config,
		storage:        storage,
		cache:          cache,
		activeLoads:    make(map[string]*LoadOperation),
		prefetchQueue:  NewPrefetchQueue(),
		stats:          &LoaderStats{},
		ctx:            ctx,
		cancel:         cancel,
	}

	// Initialize fanotify monitor if available
	if IsFanotifyAvailable() {
		monitor, err := NewFanotifyMonitor()
		if err != nil {
			return nil, fmt.Errorf("failed to create fanotify monitor: %w", err)
		}
		loader.monitor = monitor

		// Create event processor
		loader.eventProcessor = NewEventProcessor(loader)
	}

	return loader, nil
}

// Start starts the smart loader
func (l *SmartLoader) Start() error {
	if l.monitor != nil {
		if err := l.monitor.Start(l.ctx); err != nil {
			return fmt.Errorf("failed to start fanotify monitor: %w", err)
		}

		// Start event processor
		l.wg.Add(1)
		go l.eventProcessor.ProcessEvents(l.ctx)

		// Start prefetch worker
		l.wg.Add(1)
		go l.prefetchWorker(l.ctx)
	}

	return nil
}

// Stop stops the smart loader
func (l *SmartLoader) Stop() error {
	l.cancel()

	if l.monitor != nil {
		if err := l.monitor.Stop(); err != nil {
			return fmt.Errorf("failed to stop fanotify monitor: %w", err)
		}
	}

	l.wg.Wait()
	return nil
}

// Load implements on-demand loading with smart triggering
func (l *SmartLoader) Load(ctx context.Context, key string) ([]byte, error) {
	startTime := time.Now()

	// Update statistics
	l.updateStats(func(stats *LoaderStats) {
		stats.TotalRequests++
	})

	// Try cache first
	if data, err := l.cache.Get(ctx, key); err == nil {
		l.updateStats(func(stats *LoaderStats) {
			stats.CacheHits++
		})
		return data, nil
	}

	l.updateStats(func(stats *LoaderStats) {
		stats.CacheMisses++
	})

	// Create load operation
	op := l.createLoadOperation(key)

	// Start loading
	data, err := l.performLoad(ctx, op)
	if err != nil {
		op.Status = "failed"
		op.Error = err
		op.EndTime = time.Now()
		return nil, err
	}

	// Cache the result
	if err := l.cache.Set(ctx, key, data, time.Hour); err != nil {
		// Log warning but don't fail
		fmt.Printf("Warning: failed to cache data for key %s: %v\n", key, err)
	}

	// Update operation status
	op.Status = "completed"
	op.BytesLoaded = int64(len(data))
	op.EndTime = time.Now()

	// Update statistics
	loadTime := time.Since(startTime)
	l.updateStats(func(stats *LoaderStats) {
		stats.TotalBytesLoaded += int64(len(data))
		if stats.AverageLoadTime == 0 {
			stats.AverageLoadTime = loadTime
		} else {
			stats.AverageLoadTime = (stats.AverageLoadTime + loadTime) / 2
		}
	})

	// Trigger prefetch for related files
	if l.config.PrefetchEnabled {
		l.triggerPrefetch(key)
	}

	return data, nil
}

// Prefetch implements intelligent prefetching
func (l *SmartLoader) Prefetch(ctx context.Context, keys []string) error {
	l.updateStats(func(stats *LoaderStats) {
		stats.PrefetchRequests += int64(len(keys))
	})

	for _, key := range keys {
		// Check if already cached
		if exists, err := l.cache.Exists(ctx, key); err == nil && exists {
			l.updateStats(func(stats *LoaderStats) {
				stats.PrefetchHits++
			})
			continue
		}

		// Add to prefetch queue
		l.prefetchQueue.Add(key, 1) // Default priority
	}

	return nil
}

// GetProgress returns loading progress
func (l *SmartLoader) GetProgress(ctx context.Context, key string) (types.LoadingProgress, error) {
	l.loadMutex.RLock()
	op, exists := l.activeLoads[key]
	l.loadMutex.RUnlock()

	if !exists {
		return types.LoadingProgress{
			Key:    key,
			Status: "not_found",
		}, nil
	}

	return types.LoadingProgress{
		Key:         key,
		Status:      op.Status,
		BytesLoaded: op.BytesLoaded,
		BytesTotal:  op.BytesTotal,
		StartTime:   op.StartTime,
		EndTime:     op.EndTime,
		Error:       op.Error,
	}, nil
}

// GetStats returns loader statistics
func (l *SmartLoader) GetStats(ctx context.Context) (types.LoaderStats, error) {
	l.statsMutex.RLock()
	defer l.statsMutex.RUnlock()

	return types.LoaderStats{
		TotalRequests:      l.stats.TotalRequests,
		CacheHits:          l.stats.CacheHits,
		CacheMisses:        l.stats.CacheMisses,
		PrefetchRequests:   l.stats.PrefetchRequests,
		ActiveDownloads:    l.stats.ActiveDownloads,
		TotalBytesLoaded:   l.stats.TotalBytesLoaded,
		AverageLoadTime:    l.stats.AverageLoadTime,
	}, nil
}

// AddWatch adds a path to monitor for on-demand loading
func (l *SmartLoader) AddWatch(path string) error {
	if l.monitor == nil {
		return fmt.Errorf("fanotify monitor not available")
	}

	return l.monitor.AddWatch(path)
}

// RemoveWatch removes a path from monitoring
func (l *SmartLoader) RemoveWatch(path string) error {
	if l.monitor == nil {
		return fmt.Errorf("fanotify monitor not available")
	}

	return l.monitor.RemoveWatch(path)
}

// createLoadOperation creates a new load operation
func (l *SmartLoader) createLoadOperation(key string) *LoadOperation {
	ctx, cancel := context.WithCancel(l.ctx)

	op := &LoadOperation{
		Key:          key,
		Status:       "pending",
		StartTime:    time.Now(),
		ProgressChan: make(chan types.LoadingProgress, 10),
		cancel:       cancel,
	}

	l.loadMutex.Lock()
	l.activeLoads[key] = op
	l.loadMutex.Unlock()

	// Update active downloads count
	l.updateStats(func(stats *LoaderStats) {
		stats.ActiveDownloads++
	})

	// Clean up when operation completes
	go func() {
		<-ctx.Done()
		l.loadMutex.Lock()
		delete(l.activeLoads, key)
		l.loadMutex.Unlock()

		l.updateStats(func(stats *LoaderStats) {
			stats.ActiveDownloads--
		})
	}()

	return op
}

// performLoad performs the actual loading
func (l *SmartLoader) performLoad(ctx context.Context, op *LoadOperation) ([]byte, error) {
	op.Status = "loading"

	// Try to load from storage
	data, err := l.storage.LoadFile(ctx, op.Key)
	if err != nil {
		return nil, fmt.Errorf("failed to load from storage: %w", err)
	}

	op.BytesTotal = int64(len(data))
	return data, nil
}

// triggerPrefetch triggers prefetch for related files
func (l *SmartLoader) triggerPrefetch(key string) {
	// Simple heuristic: prefetch files in the same directory
	dir := filepath.Dir(key)
	ext := filepath.Ext(key)
	base := strings.TrimSuffix(filepath.Base(key), ext)

	// Generate related keys (same directory, similar names)
	relatedKeys := []string{}
	for i := 1; i <= l.config.PrefetchDepth; i++ {
		relatedKeys = append(relatedKeys,
			filepath.Join(dir, fmt.Sprintf("%s_%d%s", base, i, ext)),
			filepath.Join(dir, fmt.Sprintf("%s.%d%s", base, i, ext)),
		)
	}

	// Add to prefetch queue with lower priority
	for _, relatedKey := range relatedKeys {
		l.prefetchQueue.Add(relatedKey, 0.5)
	}
}

// prefetchWorker processes the prefetch queue
func (l *SmartLoader) prefetchWorker(ctx context.Context) {
	defer l.wg.Done()

	ticker := time.NewTicker(100 * time.Millisecond)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			// Process prefetch queue
			for i := 0; i < l.config.MaxConcurrency; i++ {
				item := l.prefetchQueue.Get()
				if item == nil {
					break
				}

				// Prefetch in background
				go func(key string) {
					prefetchCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
					defer cancel()

					if _, err := l.Load(prefetchCtx, key); err != nil {
						// Log error but continue
						fmt.Printf("Prefetch failed for key %s: %v\n", key, err)
					} else {
						l.updateStats(func(stats *LoaderStats) {
							stats.TotalBytesPrefetched++
						})
					}
				}(item.Key)
			}
		}
	}
}

// updateStats updates loader statistics
func (l *SmartLoader) updateStats(update func(*LoaderStats)) {
	l.statsMutex.Lock()
	defer l.statsMutex.Unlock()

	update(l.stats)
	l.stats.LastUpdated = time.Now()
}

// HandleFileEvent handles file system events for on-demand loading
func (l *SmartLoader) HandleFileEvent(event FileEvent) {
	l.updateStats(func(stats *LoaderStats) {
		stats.EventCount++
	})

	// Only handle access events for on-demand loading
	if event.EventType != FileAccess {
		return
	}

	// Convert path to storage key
	key := l.pathToKey(event.Path)

	// Check if file needs to be loaded
	if l.shouldTriggerLoad(key) {
		l.updateStats(func(stats *LoaderStats) {
			stats.TriggerCount++
		})

		// Trigger load in background
		go func() {
			ctx, cancel := context.WithTimeout(l.ctx, 30*time.Second)
			defer cancel()

			if _, err := l.Load(ctx, key); err != nil {
				fmt.Printf("On-demand load failed for %s: %v\n", key, err)
			}
		}()
	}
}

// pathToKey converts filesystem path to storage key
func (l *SmartLoader) pathToKey(path string) string {
	// Simple implementation - can be enhanced based on storage backend
	return path
}

// shouldTriggerLoad determines if a file should trigger on-demand loading
func (l *SmartLoader) shouldTriggerLoad(key string) bool {
	// Check if already cached
	exists, err := l.cache.Exists(l.ctx, key)
	if err == nil && exists {
		return false
	}

	// Check if already being loaded
	l.loadMutex.RLock()
	_, loading := l.activeLoads[key]
	l.loadMutex.RUnlock()

	return !loading
}